/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
    ArkFile,
    ArkMethod,
    ArkNamespace,
    ts
} from 'arkanalyzer';

import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import {
    ClassMatcher,
    FieldMatcher,
    FileMatcher,
    MatcherCallback,
    MatcherTypes,
    MethodMatcher,
} from "../../matcher/Matchers";
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoNameSpaceCheck');
export type Options = [
    {
        allowDeclarations?: boolean;
        allowDefinitionFiles?: boolean;
    },
];

type ErrorReport = {
    line: number;
    column: number;
    message: string;
};

export class NoNameSpaceCheck implements BaseChecker {
    private defaultOptions: Options = [
        {
            allowDeclarations: false,
            allowDefinitionFiles: true,
        },
    ];
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    public metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: 'docs/no-namespace-check.md',
        description: 'ES2015 module syntax is preferred over namespaces.',
    };

    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    public registerMatchers(): MatcherCallback[] {
        const matchFileCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [matchFileCb];
    }



    public check = (targetField: ArkFile) => {
        this.defaultOptions = this.rule && this.rule.option && this.rule.option[0] ? this.rule.option as Options : this.defaultOptions;
        const severity = this.rule.alert ?? this.metaData.severity;
        const filePath = targetField.getFilePath();
        let code = targetField.getCode();
        const myInvalidPositions = this.checkNoNamespace(code, this.defaultOptions);
        myInvalidPositions.forEach((pos:ErrorReport) => {
            this.addIssueReport(filePath, pos, severity)
        });
    }

    private addIssueReport(filePath: string, pos: ErrorReport, severity: number) {
        const fixKey = pos.line + '%' + pos.column + '%' + this.rule.ruleId;
        const mergeKey = filePath + '%' + fixKey + '%' + pos.message;
        const defect = new Defects(pos.line, pos.column, pos.message, severity, this.rule.ruleId,
            mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey)
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }

    private getOption(): Options {
        let option: Options;
        if (this.rule && this.rule.option) {
            option = this.rule.option as Options;
            if (option[0]) {
                if (!option[0]?.allowDeclarations) {
                    option[0].allowDeclarations = false;
                }
                if (!option[0]?.allowDefinitionFiles) {
                    option[0].allowDefinitionFiles = true;
                }
                return option;
            }
        }
        return this.defaultOptions;
    }

    private checkNoNamespace(
        code: string,
        options: Options = [{ allowDeclarations: false }]
      ): ErrorReport[] {
        const errors: ErrorReport[] = [];
        const sourceFile = ts.createSourceFile(
          'temp.ts',
          code,
          ts.ScriptTarget.Latest,
          true
        );
      
        const allowDeclarations = options[0]?.allowDeclarations ?? false;
      
        const isDeclaredModule = (node: ts.ModuleDeclaration): boolean => {
          if (node.modifiers?.some(m => m.kind === ts.SyntaxKind.DeclareKeyword)) {
            return true;
          }
          let parent: ts.Node | undefined = node.parent;
          while (parent) {
            if (ts.isModuleDeclaration(parent)) {
              if (parent.modifiers?.some(m => m.kind === ts.SyntaxKind.DeclareKeyword)) {
                return true;
              }
            }
            parent = parent.parent;
          }
          return false;
        };
      
        const traverse = (node: ts.Node): void => {
          if (ts.isModuleDeclaration(node)) {
            const declared = isDeclaredModule(node);
            const isStringModule = ts.isStringLiteral(node.name);
      
            let shouldReport = false;
      
            if (allowDeclarations) {
              shouldReport = !declared;
            } else {
              if (declared) {
                shouldReport = !isStringModule;
              } else {
                shouldReport = true;
              }
            }
      
            if (shouldReport) {
              const start = node.getStart();
              const { line: tsLine, character: tsChar } =
                sourceFile.getLineAndCharacterOfPosition(start);
              errors.push({
                line: tsLine + 1,
                column: tsChar + 1,
                message: 'ES2015 module syntax is preferred over namespaces.',
              });
            }
          }
          ts.forEachChild(node, traverse);
        };
      
        traverse(sourceFile);
        return errors;
      }

}